# @Time : 2021/4/6 9:37
# @Author : Fioman
# @Phone : 13149920693
"""
线阵相机的驱动
"""
from ctypes import *

import gevent
import numpy as np
from tools.hd_logger import HdLogger
from tools.config_params import ConfigParams
from settings import CAMERA_LIB_PATH

INFO_MAX_BUFFER_SIZE = 64
MV_MAX_DEVICE_NUM = 256
MV_GIGE_DEVICE = 0x00000001
MV_ACCESS_Exclusive = 1

def get_error_info(fn):
    """
    装饰器,解析错误码的装饰器
    :param fn: 调用的相机驱动API
    :return:
    """
    
    def get_info_str(*args):
        ret = fn(*args)
        try:
            errStr = '0x' + (str('%x' % ret)).upper()
            res = errorDict.get(errStr, "相机的错误字典中,没有收录对应的错误信息")
            if "OK" in res:
                res = 0
            return res
        except Exception as e:
            errorInfo = "in get_error_info() error : {}".format(str(e))
            print(errorInfo)
            return errorInfo
    
    return get_info_str


def get_cam_IP(deviceIp, netIp):
    stDevInfo = MV_CC_DEVICE_INFO()
    stGigEDev = MV_GIGE_DEVICE_INFO()
    deviceIpList = deviceIp.split('.')
    stGigEDev.nCurrentIp = (int(deviceIpList[0]) << 24) | (int(deviceIpList[1]) << 16) | (int(deviceIpList[2]) << 8) | int(deviceIpList[3])
    
    netIpList = netIp.split('.')
    stGigEDev.nNetExport =  (int(netIpList[0]) << 24) | (int(netIpList[1]) << 16) | (int(netIpList[2]) << 8) | int(netIpList[3])
    
    stDevInfo.nTLayerType = MV_GIGE_DEVICE
    stDevInfo.SpecialInfo.stGigEInfo = stGigEDev
    return stDevInfo

errorDict = {
    "0x0": "MV_OK	成功，无错误",
    "0x80000000": "MV_E_HANDLE	错误或无效的句柄",
    "0x80000001": "MV_E_SUPPORT	不支持的功能",
    "0x80000002": "MV_E_BUFOVER	缓存已满",
    "0x80000003": "MV_E_CALLORDER	函数调用顺序错误",
    "0x80000004": "MV_E_PARAMETER	错误的参数",
    "0x80000006": "MV_E_RESOURCE	资源申请失败",
    "0x80000007": "MV_E_NODATA	无数据",
    "0x80000008": "MV_E_PRECONDITION	前置条件有误，或运行环境已发生变化",
    "0x80000009": "MV_E_VERSION	版本不匹配",
    "0x8000000A": "MV_E_NOENOUGH_BUF	传入的内存空间不足",
    "0x8000000B": "MV_E_ABNORMAL_IMAGE	异常图像，可能是丢包导致图像不完整",
    "0x8000000C": "MV_E_LOAD_LIBRARY	动态导入DLL失败",
    "0x800000FF": "MV_E_UNKNOW	未知的错误",
    "0x80000100": "MV_E_GC_GENERIC	通用错误",
    "0x80000101": "MV_E_GC_ARGUMENT	参数非法",
    "0x80000102": "MV_E_GC_RANGE	值超出范围",
    "0x80000103": "MV_E_GC_PROPERTY	属性",
    "0x80000104": "MV_E_GC_RUNTIME	运行环境有问题",
    "0x80000105": "MV_E_GC_LOGICAL	逻辑错误",
    "0x80000106": "MV_E_GC_ACCESS	节点访问条件有误",
    "0x80000107": "MV_E_GC_TIMEOUT	超时",
    "0x80000108": "MV_E_GC_DYNAMICCAST	转换异常",
    "0x800001FF": "MV_E_GC_UNKNOW	GenICam未知错误",
    "0x80000200": "MV_E_NOT_IMPLEMENTED	命令不被设备支持",
    "0x80000201": "MV_E_INVALID_ADDRESS	访问的目标地址不存在",
    "0x80000202": "MV_E_WRITE_PROTECT	目标地址不可写",
    "0x80000203": "MV_E_ACCESS_DENIED	设备无访问权限",
    "0x80000204": "MV_E_BUSY	设备忙，或网络断开",
    "0x80000205": "MV_E_PACKET	网络包数据错误",
    "0x80000206": "MV_E_NETER	网络相关错误",
    "0x80000221": "MV_E_IP_CONFLICT	设备IP冲突",
    "0x80000300": "MV_E_USB_READ	读usb出错",
    "0x80000301": "MV_E_USB_WRITE	写usb出错",
    "0x80000302": "MV_E_USB_DEVICE	设备异常",
    "0x80000303": "MV_E_USB_GENICAM	GenICam相关错误",
    "0x80000304": "MV_E_USB_BANDWIDTH	带宽不足",
    "0x800003FF": "MV_E_USB_UNKNOW	USB未知的错误",
    "0x80000400": "MV_E_UPG_FILE_MISMATCH	升级固件不匹配",
    "0x80000401": "MV_E_UPG_LANGUSGE_MISMATCH	升级固件语言不匹配",
    "0x80000402": "MV_E_UPG_CONFLICT	升级冲突（设备已经在升级了再次请求升级即返回此错误）",
    "0x80000403": "MV_E_UPG_INNER_ERR	升级时相机内部出现错误",
    "0x800004FF": "MV_E_UPG_UNKNOW	升级时未知错误"
}


class MvDeviceInfoList(Structure):
    _fields_ = [
        ("nDeviceNum", c_uint32),
        ("pDeviceInfo", c_void_p * 256)
    ]


class MvFrameTimeoutInfoList(Structure):
    _fields_ = [
        ("nWidth", c_uint16),
        ("nHeight", c_uint16),
        ("enPixelType", c_uint),
        ("nFrameNum", c_uint),
        ("nDevTimeStampHigh", c_uint),
        ("nDevTimeStampLow", c_uint),
        ("nReserved0", c_uint),
        ("nHostTimeStamp", c_int64),
        ("nFrameLen", c_uint),
        ("nSecondCount", c_uint),
        ("nCycleCount", c_uint),
        ("nCycleOffset", c_uint),
        ("fGain", c_float),
        ("fExposureTime", c_float),
        ("nAverageBrightness", c_uint),
        ("nRed", c_uint),
        ("nGreen", c_uint),
        ("nBlue", c_uint),
        ("nFrameCounter", c_uint),
        ("nTriggerIndex", c_uint),
        ("nInput", c_uint),
        ("nOutput", c_uint),
        ("nOffsetX", c_uint16),
        ("nOffsetY", c_uint16),
        ("nReserved", c_uint * 41)
    ]


# CameraParams.h 80
class _MV_CC_DEVICE_INFO_(Structure):
    pass

# GigE设备信息    \~english GigE device info
class _MV_GIGE_DEVICE_INFO_(Structure):
    pass
_MV_GIGE_DEVICE_INFO_._fields_ = [
    ('nIpCfgOption', c_uint),                     # < \~chinese IP配置选项         \~english Ip config option
    ('nIpCfgCurrent', c_uint),                    # < \~chinese 当前IP地址配置     \~english IP configuration:bit31-static bit30-dhcp bit29-lla
    ('nCurrentIp', c_uint),                       # < \~chinese 当前主机IP地址     \~english Current host Ip
    ('nCurrentSubNetMask', c_uint),               # < \~chinese 当前子网掩码       \~english curtent subnet mask
    ('nDefultGateWay', c_uint),                   # < \~chinese 默认网关           \~english Default gate way
    ('chManufacturerName', c_ubyte * 32),         # < \~chinese 厂商名称           \~english Manufacturer Name
    ('chModelName', c_ubyte * 32),                # < \~chinese 型号名称           \~english Mode name
    ('chDeviceVersion', c_ubyte * 32),            # < \~chinese 设备固件版本       \~english Device Version
    ('chManufacturerSpecificInfo', c_ubyte * 48), # < \~chinese 厂商特殊信息       \~english Manufacturer Specific Infomation
    ('chSerialNumber', c_ubyte * 16),             # < \~chinese 序列号            \~english serial number
    ('chUserDefinedName', c_ubyte * 16),          # < \~chinese 用户定义名称       \~english User Defined Name
    ('nNetExport', c_uint),                       # < \~chinese 网口Ip地址         \~english NetWork Ip address
    ('nReserved', c_uint * 4),                    # < \~chinese 保留字节           \~english Reserved bytes
]
MV_GIGE_DEVICE_INFO = _MV_GIGE_DEVICE_INFO_

# USB设备信息    \~english USB device info
class _MV_USB3_DEVICE_INFO_(Structure):
    pass
_MV_USB3_DEVICE_INFO_._fields_ = [
    ('CrtlInEndPoint', c_ubyte),                            # < \~chinese 控制输入端点          \~english Control input endpoint
    ('CrtlOutEndPoint', c_ubyte),                           # < \~chinese 控制输出端点          \~english Control output endpoint
    ('StreamEndPoint', c_ubyte),                            # < \~chinese 流端点                \~english Flow endpoint
    ('EventEndPoint', c_ubyte),                             # < \~chinese 事件端点              \~english Event endpoint
    ('idVendor', c_ushort),                                 # < \~chinese 供应商ID号            \~english Vendor ID Number
    ('idProduct', c_ushort),                                # < \~chinese 产品ID号              \~english Device ID Number
    ('nDeviceNumber', c_uint),                              # < \~chinese 设备序列号            \~english Device Serial Number
    ('chDeviceGUID', c_ubyte * INFO_MAX_BUFFER_SIZE),       # < \~chinese 设备GUID号            \~english Device GUID Number
    ('chVendorName', c_ubyte * INFO_MAX_BUFFER_SIZE),       # < \~chinese 供应商名字            \~english Vendor Name
    ('chModelName', c_ubyte * INFO_MAX_BUFFER_SIZE),        # < \~chinese 型号名字              \~english Model Name
    ('chFamilyName', c_ubyte * INFO_MAX_BUFFER_SIZE),       # < \~chinese 家族名字              \~english Family Name
    ('chDeviceVersion', c_ubyte * INFO_MAX_BUFFER_SIZE),    # < \~chinese 设备版本号            \~english Device Version
    ('chManufacturerName', c_ubyte * INFO_MAX_BUFFER_SIZE), # < \~chinese 制造商名字            \~english Manufacturer Name
    ('chSerialNumber', c_ubyte * INFO_MAX_BUFFER_SIZE),     # < \~chinese 序列号                \~english Serial Number
    ('chUserDefinedName', c_ubyte * INFO_MAX_BUFFER_SIZE),  # < \~chinese 用户自定义名字         \~english User Defined Name
    ('nbcdUSB', c_uint),                                    # < \~chinese 支持的USB协议         \~english Support USB Protocol
    ('nDeviceAddress', c_uint),                             # < \~chinese 设备地址              \~english Device Address
    ('nReserved', c_uint * 2),                              # < \~chinese 保留字节              \~english Reserved bytes
]
MV_USB3_DEVICE_INFO = _MV_USB3_DEVICE_INFO_

# CameraLink设备信息    \~english CameraLink device info
class _MV_CamL_DEV_INFO_(Structure):
    pass
_MV_CamL_DEV_INFO_._fields_ = [
    ('chPortID', c_ubyte * INFO_MAX_BUFFER_SIZE),           # < \~chinese 端口号            \~english Port ID
    ('chModelName', c_ubyte * INFO_MAX_BUFFER_SIZE),        # < \~chinese 设备型号          \~english Model name
    ('chFamilyName', c_ubyte * INFO_MAX_BUFFER_SIZE),       # < \~chinese 家族名字          \~english Family Name
    ('chDeviceVersion', c_ubyte * INFO_MAX_BUFFER_SIZE),    # < \~chinese 设备版本号        \~english Device Version
    ('chManufacturerName', c_ubyte * INFO_MAX_BUFFER_SIZE), # < \~chinese 制造商名字        \~english Manufacturer Name
    ('chSerialNumber', c_ubyte * INFO_MAX_BUFFER_SIZE),     # < \~chinese 序列号            \~english Serial Number
    ('nReserved', c_uint * 38),                             # < \~chinese 保留字节          \~english Reserved bytes
]
MV_CamL_DEV_INFO = _MV_CamL_DEV_INFO_
# 设备信息    \~english Device info
class N19_MV_CC_DEVICE_INFO_3DOT_0E(Union):
    pass

N19_MV_CC_DEVICE_INFO_3DOT_0E._fields_ = [
    ('stGigEInfo', MV_GIGE_DEVICE_INFO),                   # < \~chinese Gige设备信息        \~english Gige device infomation
    ('stUsb3VInfo', MV_USB3_DEVICE_INFO),                  # < \~chinese U3V设备信息         \~english u3V device information
    ('stCamLInfo', MV_CamL_DEV_INFO),                      # < \~chinese CamLink设备信息     \~english CamLink device information
]

_MV_CC_DEVICE_INFO_._fields_ = [
    # CameraParams.h 80
    ('nMajorVer', c_ushort),                              # < \~chinese 规范的主要版本         \~english Major version of the specification.
    ('nMinorVer', c_ushort),                              # < \~chinese 规范的次要版本         \~english Minor version of the specification
    ('nMacAddrHigh', c_uint),                             # < \~chinese MAC地址高位           \~english Mac address high
    ('nMacAddrLow', c_uint),                              # < \~chinese MAC地址低位           \~english Mac address low
    ('nTLayerType', c_uint),                              # < \~chinese 设备传输层协议类型     \~english Device Transport Layer Protocol Type, e.g. MV_GIGE_DEVICE
    ('nReserved', c_uint * 4),                            # < \~chinese 保留字节              \~english Reserved bytes
    ('SpecialInfo', N19_MV_CC_DEVICE_INFO_3DOT_0E),       # < \~chinese 不同设备特有信息       \~english Special information
]
MV_CC_DEVICE_INFO = _MV_CC_DEVICE_INFO_

# 设备信息列表    \~english Device Information List
class _MV_CC_DEVICE_INFO_LIST_(Structure):
    pass
_MV_CC_DEVICE_INFO_LIST_._fields_ = [
    ('nDeviceNum', c_uint),                                          # < \~chinese 在线设备数量         \~english Online Device Number
    ('pDeviceInfo', POINTER(MV_CC_DEVICE_INFO) * MV_MAX_DEVICE_NUM), # < \~chinese 支持最多256个设备    \~english Support up to 256 devices
]
MV_CC_DEVICE_INFO_LIST = _MV_CC_DEVICE_INFO_LIST_

class AreaCamera(object):
    def __init__(self, cameraIP, netIP):
        """
        初始化,将cp和logger传递进来,cp就是一些相机配置参数,logger用来记录日志
        :param cameraId:相机的序列id,序列id是每个相机自带的,根据序列id进行排序,id从0开始.
        """
        self.stDevInfo = get_cam_IP(cameraIP, netIP)
        self.logger = HdLogger()
        try:
            self.api = cdll.LoadLibrary(CAMERA_LIB_PATH)
        except Exception as e:
            self.api = None
            errorInfo = "面阵相机驱动加载失败,error : {}".format(str(e))
            print(errorInfo)
            self.logger.debug(errorInfo)
        
        self._handle = c_void_p()  # 记录当前连接设备的句柄
        self.handler = pointer(self._handle)  # 创建句柄指针
        self.camWidth = 3072
        self.camHeight = 2048
        self.deviceList = MvDeviceInfoList()
        self.bufferSize = (self.camWidth * self.camHeight) * sizeof(c_char)
        self.keepRawBuffer = create_string_buffer(self.bufferSize)
        self.frameTimeoutInfo = MvFrameTimeoutInfoList()
    
    
    @get_error_info
    def enum_devices(self):
        """
        枚举子网传输协议对应的所有的设备.
        :return:
        """
        tLayerType = 1  # Gige设备
        self.api.MV_CC_EnumDevices.restype = c_uint
        self.api.MV_CC_EnumDevices.argtype = (c_uint, pointer(self.deviceList))
        return self.api.MV_CC_EnumDevices(c_uint(tLayerType), byref(self.deviceList))
    
    @get_error_info
    def create_handler(self):
        """
        创建设备操作句柄,根据id号
        :return:
        """
        self.api.MV_CC_CreateHandle.argtype = (c_void_p, c_void_p)
        self.api.MV_CC_CreateHandle.restype = c_uint
        return self.api.MV_CC_CreateHandle(byref(self.handler), byref(self.stDevInfo))
    
    @get_error_info
    def close_device(self):
        """
        通过MV_CC_OpenDevice连接设备之后,可以公国该节后断开设备连接,释放资源
        :return: 成功返回MV_OK,失败返回错误码
        """
        self.api.MV_CC_CloseDevice.argtype = c_uint64
        self.api.MV_CC_CloseDevice.restype = c_uint
        return self.api.MV_CC_CloseDevice(self.handler)
    
    @get_error_info
    def destroy_handler(self):
        """
        销毁设备的实例,以及相应的资源
        :return: 成功返回MV_OK(0),失败返回错误码
        """
        self.api.MV_CC_DestroyHandle.argtype = c_uint64
        self.api.MV_CC_DestroyHandle.restype = c_uint
        return self.api.MV_CC_DestroyHandle(self.handler)
    
    @get_error_info
    def open_device(self):
        """
        打开设备,连接设备.1表示打开的设备的模式是独占模式,一般就是用此模式
        :return:
        """
        openMode = 1
        self.api.MV_CC_OpenDevice.argtype = (c_uint64, c_uint32, c_uint16)
        self.api.MV_CC_OpenDevice.restype = c_uint
        return self.api.MV_CC_OpenDevice(self.handler, c_uint32(openMode), c_uint16(0))
    
    @get_error_info
    def clear_buffer(self):
        """
        清空设备的缓存
        :return:
        """
        self.api.MV_CC_ClearImageBuffer.argtype = c_uint64
        self.api.MV_CC_ClearImageBuffer.restype = c_uint
        return self.api.MV_CC_ClearImageBuffer(self.handler)
    
    @get_error_info
    def set_device_exposure_time(self, exposureTime):
        """
        设置相机设备的曝光时间
        :param exposureTime: 曝光时间
        :return:
        """
        self.api.MV_CC_SetExposureTime.argtype = (c_uint64, c_float)
        self.api.MV_CC_SetExposureTime.restype = c_uint
        return self.api.MV_CC_SetExposureTime(self.handler, c_float(exposureTime))
    
    @get_error_info
    def set_device_image_height(self):
        """
        设置相机拍摄的高度
        :return:
        """
        self.api.MV_CC_SetExposureTime.argtype = (c_uint64, c_uint32)
        self.api.MV_CC_SetExposureTime.restype = c_uint
        return self.api.MV_CC_SetHeight(self.handler, c_uint32(self.camHeight))
    
    def get_image(self):
        """
        获取图像
        :return:
        """
        timeOut = 10000  # 超时时间,单位毫秒
        ret = self.get_one_frame_with_timeout(timeOut)
        if ret != 0:
            print("in LineCamera.get_image() error : {}".format(ret))
            return -1, ret
        
        image = self.get_image_buffer()[1]
        return 0, image
    
    @get_error_info
    def get_one_frame_with_timeout(self, timeOut):
        """
        获取一帧的图片,使用超时机制,超时时间限定是10秒.
        :return:
        """
        self.api.MV_CC_GetOneFrameTimeout.argtype = (c_uint64, c_char_p, c_uint, POINTER(MvFrameTimeoutInfoList), c_int)
        self.api.MV_CC_GetOneFrameTimeout.restype = c_uint
        return self.api.MV_CC_GetOneFrameTimeout(self.handler, byref(self.keepRawBuffer), self.bufferSize,
                                                 byref(self.frameTimeoutInfo), c_int(timeOut))
    
    def get_image_buffer(self):
        """
        获取图片的保存的buffer,转换成一张图片.
        :return:
        """
        try:
            buffer = np.frombuffer(self.keepRawBuffer, c_uint8)[0:self.camWidth * self.camHeight]
            buffer = buffer.reshape(self.camHeight, self.camWidth)
        except Exception as e:
            errorInfo = "in get_image_buffer() error : {}".format(str(e))
            print(errorInfo)
            self.logger.debug(errorInfo)
            return str(e), None
        return 0, buffer
    
    @get_error_info
    def start_grabbing(self):
        """
        相机开抓图打开
        :return:
        """
        self.api.MV_CC_StartGrabbing.argtype = c_uint64
        self.api.MV_CC_StartGrabbing.restype = c_uint
        return self.api.MV_CC_StartGrabbing(self.handler)
    
    @get_error_info
    def stop_grabbing(self):
        """
        相机抓图关闭
        :return:
        """
        self.api.MV_CC_StopGrabbing.argtype = c_uint64
        self.api.MV_CC_StopGrabbing.restype = c_uint
        return self.api.MV_CC_StopGrabbing(self.handler)
    
    def is_device_online(self):
        """
        设备是否是在线连接状态
        :return:
        """
        self.api.MV_CC_IsDeviceConnected.argtype = c_uint64
        self.api.MV_CC_IsDeviceConnected.restype = c_bool
        return self.api.MV_CC_IsDeviceConnected(self.handler)
    
    def safe_close(self):
        """
        安全关闭相机,断开相机的连接,最后要消除句柄的连接.
        :return:
        """
        try:
            self.stop_grabbing()
            self.close_device()
        except Exception as e:
            errorInfo = "in LineCamera.safe_close() error: {}".format(str(e))
            print(errorInfo)
            self.logger.debug(errorInfo)
        finally:
            self.destroy_handler()
    
    def re_connect_area_camera(self):
        """
        重新连接设备
        :return:
        """
        try:
            self.safe_close()
            gevent.sleep(0.1)
        except Exception as e:
            errorInfo = "in AreaCamera.re_connect_area_camera,error: {}".format(str(e))
            print(errorInfo)
            self.logger.debug(errorInfo)
        self.create_handler()
        return self.open_device()


if __name__ == '__main__':
    import cv2 as cv
    cameraIP = '192.168.0.21'
    netIP = '192.168.0.2'
    cam = AreaCamera(cameraIP, netIP)
    ret = cam.re_connect_area_camera()
    cam.clear_buffer()
    ret2 = cam.start_grabbing()
    ret3, Image = cam.get_image()
    cv.namedWindow("1")
    cv.imshow("1", Image)
    cv.waitKey(0)
    ret4 = cam.stop_grabbing()


